-- SlimeEventM
-- created by panyl
-- 冈布奥触发事件

module("SlimeEventM", package.seeall);

local events = {};
local ruleMap = {};

local rules = {};

local eventTriggerCache = {};

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    events = {};
    ruleMap = {};

    local slime_event_CSV = get_slime_event_CSV();
    for i = 1, #slime_event_CSV do
        local row = slime_event_CSV[i];
        events[row.id] = row;

        -- 解析条件
        row["condition"] = parseEventCond(row);

        ruleMap[row.rule] = ruleMap[row.rule] or {};
        table.insert(ruleMap[row.rule], row.id);
    end

    -- 为了保证服务器/客户端的一致性，需要排序
    for rule, _ in pairs(ruleMap) do
        table.sort(ruleMap[rule]);
    end
end

-- 模块初始化操作
function init()
    if not _init then
        loadCsv();

        -- 载入所有的规则处理子模块
        rules = LOAD_PATH("game/logic/module/slime_events");
    end
end

-- 解析下事件条件
function parseEventCond(row)
    local desc = row.condition;
    if type(desc) == "number" then
        return desc;
    end

    if not desc or #desc == 0 then
        return;
    end

    -- 应该是xxx(a,b...) 这样的规则
    local len = string.len(desc);
    local s, e = string.find(desc, "%a+%(");
    local ret = { string.sub(desc, 1, e - 1), };
    local arr = string.explode(string.sub(desc, e + 1, len - 1), ",");
    for index, _ in pairs(arr) do
        table.insert(ret, tonumber(arr[index]));
    end

    return ret;
end

function query(eventId, path)
    local m = events[eventId];

    if not m or not path then
        return m;
    end

    return m[path];
end

function getIdsByRule(rule)
    return ruleMap[rule] or {};
end

-- 增加进度
function addProgress(rule, progress)
    progress = progress or 1;

    local slimeEvent = ME.user.dbase:query("slime_event", {});

    slimeEvent[rule] = (slimeEvent[rule] or 0) + progress;

    ME.user.dbase:set("slime_event", slimeEvent);
end

-- 获取进度
function getProgress(rule)
    local slimeEvent = ME.user.dbase:query("slime_event", {});

    return slimeEvent[rule] or 0;
end

-- 获取绑定的冈布奥
function fetchPetForTalk(eventId, para)
    -- 带了冈布奥id
    local id = para["class_id"];
    if type(id) == "number" and PetM.query(id) then
        return id;
    end

    -- 从配置表中获取
    local dbase = query(eventId, "dbase");
    if dbase["pet_id"] then
        return dbase["pet_id"];
    end

    -- 当前出战的宠物
    local pet = ME.user:getActivePet();
    return pet.classId;
end

-- 对话内容
function fetchTalkContent(eventId, para)
    -- TODO: 返回一个文本数组
    local talk = query(eventId, "talk");
    if type(talk) == 'string' then
        talk = {talk};
    elseif type(talk) == 'table' then
        talk = table.deepcopy(talk);
    end
    return talk;
end

-- 判断能否触发事件
function checkEvent(eventId, para)
    local rule = query(eventId, "rule");
    local mod = rules[rule];

    -- 优先交给子模块判断
    if mod.checkEvent then
        return mod.checkEvent(eventId, para);
    end

    local cond = query(eventId, "condition");
    -- 如果没有条件
    if nil == cond or cond == "" then
        return true;
    end

    if type(cond) == "table" then
        if cond[1] == "start" then
            local progress = getProgress(rule);
            if progress == cond[2] or
                #cond > 2 and progress > cond[2] and ((progress - cond[2]) % cond[3] == 0) then
                return true;
            end
        end
    end

    return false;
end

-- 计算事件奖励
function calcEventBonus(eventId, para)
    local bonus = query(eventId, "bonus");

    if type(bonus) == "table" then
        return bonus;
    end

    local formula;
    if type(bonus) == "string" and string.startWith(bonus, "formula_") then
        formula = string.sub(bonus, string.len("formula_") + 1);
        return FormulaM.invoke(formula, eventId, para);
    end

    return {};
end

-- 触发事件
function triggerEvent(eventId, para)
    para = para or {};

    -- 先判断下
    if not checkEvent(eventId, para) then
        return false;
    end

    cclog("触发了%d冈布奥事件。", eventId);

    -- 先子模块处理
    local rule = query(eventId, "rule");
    local mod = rules[rule];

    local bonusTip = query(eventId, "bonus_tip") or "";
    local petId = fetchPetForTalk(eventId, para);
    bonusTip = string.gsub(bonusTip, "{pet}", PetM.query(petId, "name"));

    local bonusList = {};
    if nil == mod.triggerEvent or
        not mod.triggerEvent(eventId, para) then
        -- 子模块没处理，走默认流程
        local arr = calcEventBonus(eventId, para);
        if type(arr) ~= "table" or #arr <= 0 then
            return false;
        end

        -- 如果有配合需要扣除的
        -- 要先尝试扣除
        if not tryCost(eventId, para) then
            -- 扣除失败了
            return false;
        end

        if type(arr[1]) ~= "table" then
            arr = { arr, };
        end

        bonusList = arr;
        for _, bonus in pairs(arr) do
            -- 先收集提示
            if bonus[1] == 1 then
                -- 物品
                bonusTip = string.gsub(bonusTip, "{bonus}", ItemM.query(bonus[2], "name"));
            elseif bonus[1] == 2 then
                -- 属性
                bonusTip = string.gsub(bonusTip, "{bonus}", FieldsM.getFieldName(bonus[2]));
            end

            bonusTip = string.gsub(bonusTip, "{count}", bonus[3]);
        end
    end

    -- 奖励回调（玩家点了确认之后再做奖励）
    local function bonusCallback()
        for _, bonus in pairs(bonusList) do
            -- 如果是showBonus的就不要奖励了
            local dbase = query(eventId, "dbase") or {};
            if bonus == dbase["show_bonus"] then
                return;
            end
            BonusM.doBonus(bonus, "slime_event_" .. eventId);
        end
    end

    -- 抛出对话
    local dbase = query(eventId, "dbase") or {};

    if #bonusList == 0 and dbase["show_bonus"] then
        bonusList = { dbase["show_bonus"] };
    end

    -- 迷宫中
    local isInDungeon = (DungeonM.getState() == DUNGEON_GAMING);

    local bonusTitle = query(eventId, "bonus_title");
    if not bonusTitle or #bonusTitle <= 0 then
        bonusTitle = nil;
    end

    -- TODO:
    if isInDungeon and isVerifyClient() then
        bonusCallback();
    end

    -- TODO: 下面的代码太绕了，需要整改
    if dbase["no_talk"] and dbase["dungeon_bonus"] then
        -- 直接抛一个表现事件
        EventMgr.fire(event.SLIME_EVENT_EFFECT, {
            ["eventId"]   = eventId,
            ["bonusTip"]  = bonusTip,
            ["callback"]  = bonusCallback,
            ["bonusList"] = bonusList,
            ["pos"]       = para["pos"],
        });
    elseif not dbase["no_talk"] then
        -- 需要冈布奥对话的
        if dbase["tip_first"] then
            doAlert(bonusTip);
        end

        -- 有配置对话模型的
        local modelId;
        local heroName;
        if type(dbase["model_id"]) == "number" then
            modelId = dbase["model_id"];
        elseif type(dbase["model_id"]) == "string" then
            modelId = FormulaM.invoke(dbase["model_id"], eventId, para);
            heroName = FormulaM.invoke(dbase["model_id"], eventId, para, true);
        end

        -- 抛出冈布奥对话事件
        EventMgr.fire(event.SLIME_TALK, {
                ["eventId"] = eventId,
                ["heroId"] = petId,
                ["msgList"] = fetchTalkContent(eventId, para),
                ["endEffect"] = iif(isInDungeon, nil, bonusList),
                ["bonusTitle"] = bonusTitle,
                ["exitFunc"]   = iif(isInDungeon, nil, bonusCallback),
                ["modelId"] = modelId,
                ["heroName"] = heroName,
                ["para"] = para,
                ["endCallback"] = function()
                    if not dbase["tip_first"] then
                        doAlert(bonusTip);
                    end

                    -- 如果是在迷宫中
                    if isInDungeon then
                        bonusCallback();

                        -- 完成对话后抛出捡物品事件
                        EventMgr.fire(event.PICK_UP_ITEM, {
                            ["bonus"]         = bonusList,
                            ["pos"]           = para["pos"] or 25,
                            ["newBonus"]      = true,
                            ["type"]          = GRID_TYPE_ITEM,
                            ["isItemVisible"] = true,
                            ["borrowGrid"]    = true,
                        });
                    end
                end,
            });

        AudioM.playFx("slime_alchemist_app");
    else
        bonusCallback();
    end

    -- 抛出事件
    EventMgr.fire(event.SLIME_EVENT, {
        ["eventId"]     = eventId,
        ["bonusList"]   = bonusList,
        ["heroId"]      = petId,
        ["msgList"]     = fetchTalkContent(eventId, para),
        ["bonusTip"]    = bonusTip,
        ["showTalk"]    = dbase["no_talk"] == nil,
    });

    -- 如果是冈布奥，抛出事件，尝试完成成就：获得隐藏冈布奥
    if dbase["slime"] then
        EventMgr.fire(event.GET_HIDDEN_SLIME, { ["heroId"] = petId, });
    end

    return true;
end

-- 配置了扣除的需要尝试扣除
function tryCost(eventId, para)
    local bonusArgs = SlimeEventM.query(eventId, "bonus_args");
    local costs = bonusArgs["cost"];
    if type(costs) ~= "table" then
        -- 没有配置扣除
        return true;
    end

    -- 扣除
    for _, cost in pairs(costs) do
        if cost[1] == 1 then
            -- 物品
            if not ItemM.costAmount(ME.user, cost[2], cost[3]) then
                -- 扣除失败
                return false;
            end
        elseif cost[1] == 2 then
            -- 属性
            if ME.user.dbase:query(cost[2], 0) < cost[3] then
                -- 不够扣除
                return false;
            end
            ME.user:costAttrib(cost[2], cost[3]);
        end
    end

    -- 扣除成功了
    return true;
end

-- 尝试触发地牢内冈布奥事件
function triggerDungeonSlimeEvent(pos, eventId)
    local grid = DungeonM.getGridByPos(pos);

    if grid.type ~= GRID_TYPE_SLIME_EVENT then
        -- 不是冈布奥事件格子
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        -- 格子失效了
        return false;
    end

    if type(eventId) ~= "number" then
        -- 没有触发事件ID
        return false;
    end

    local dbase = DungeonM.query(grid.class, "dbase");
    local eventIds = dbase["slime_events"] or {};
    if table.indexOf(eventIds, eventId) == -1 then
        -- 不是可以触发的类型
        return false;
    end

    -- 获取格子参数
    local elementId = grid.element;
    local para = DungeonM.queryElement(elementId).dbase;
    para.pos = pos;

    -- 尝试触发事件
    if not triggerEvent(eventId, para) then
        return false;
    end

    -- 改变状态
    grid:changeState(GRID_STATE_DISABLE);

    -- 立马保存进度
    DungeonActionM.immediatelySave();

    return true;
end

-- 迷宫内冈布奥通用判断
function checkEventForDungeonSlime(eventId, para)
    -- 检查冈布奥相关
    local dbase = query(eventId, "dbase");

    -- 如果是冈布奥
    if dbase["slime"] and ME.user.pets[dbase["slime"]] then
        return false;
    end

    -- 如果是碎片
    if dbase["slime_scrap"] and not ME.user.pets[dbase["slime_scrap"]] then
        return false;
    end

    -- 配置了需要物品
    if type(para["need_property"]) == "number" then
        if ItemM.getAmount(ME.user, para["need_property"]) <= 0
            and not EquipM.isEquiped(para["need_property"]) then
            -- 没有物品
            return false;
        end
    elseif type(para["need_property"]) == "table" then
        for _, classId in pairs(para["need_property"]) do
            if ItemM.getAmount(ME.user, classId) <= 0
                and not EquipM.isEquiped(para["need_property"]) then
                return false;
            end
        end
    end

    -- 需要物品之一
    if type(para["need_property_one"]) == "table" then
        local hasNeedOne = false;
        for _, classId in pairs(para["need_property_one"]) do
            if ItemM.getAmount(ME.user, classId) > 0
                or EquipM.isEquiped(para["need_property"]) then
                hasNeedOne = true;
                break;
            end
        end

        if not hasNeedOne then
            -- 没有物品之一
            return false;
        end
    end

    return true;
end

-- 冈布奥提交错误物品
function submitWrongItem(classId, pos)
    ItemM.costAmount(ME.user, classId, 1);
    return true;
end

-- 触发奖励魂魄
function soulItemTrigger(pos)
    local grid = DungeonM.getGridByPos(pos);

    if grid.type ~= GRID_TYPE_SLIME_EVENT then
        -- 不是史莱姆事件格子
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        -- 格子失效了
        return false;
    end

    local dbase = DungeonM.query(grid.class, "dbase");
    local bonus = dbase["soul_item"];
    if type(bonus) ~= "table" then
        -- 没有配置灵魂物品
        return false;
    end

    -- 尝试触发属性rummage_soul
    local props = PropM.fetchProps(ME.user, "rummage_soul");
    for _, prop in ipairs(props) do
        local para = { ["grid"] = grid, };
        PropM.trigger(ME.user, prop[1], prop[2], para);
    end

    -- 改变状态
    grid:changeState(GRID_STATE_DISABLE);
    return true;
end

-- 缓存冈布奥事件
function cacheEvent(eventId, para)
    -- 没有该事件
    if not query(eventId) then
        return;
    end

    if not para then
        para = {};
    end

    local eventInfo = { [eventId] = para };

    table.insert(eventTriggerCache, eventInfo);
end

-- 触发缓存的冈布奥事件
function triggerCacheEvents()
    for _, eventInfo in ipairs(eventTriggerCache) do
        local eventId = table.keys(eventInfo)[1];
        local para    = eventInfo[eventId];

        triggerEvent(eventId, para);
    end

    -- 清空
    eventTriggerCache = {};
end
